package com.aloaproject.ciquta;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * Implementation of {@link Criteria}.
 * @author Daniele Demichelis
 */
public final class CriteriaImpl implements Criteria {
	
	private final Collection collection;
    private final AndCriterion criterions;
    private Order order;
	private Projection projection;
	
	public static Criteria forCollection(final Collection collection) {
		CriteriaImpl criteria = new CriteriaImpl(collection);
		return criteria;
	}

	private CriteriaImpl(final Collection collection){
		this.collection = collection;
        this.criterions = new AndCriterion();
        this.order = null;
        this.projection = new CommonProjection();
	}
    
	public final Criteria add(final Criterion criterion) {
        if(criterion == null){
            throw new IllegalArgumentException("Criterion cannot be null.");
        }

        this.criterions.add(criterion);
		return this;
	}

	public final List list() {
        //TODO: Maybe it's not always a good idea to use an ArrayList.
        //TODO: Maybe the programmer would provide himself a specific collection
		ArrayList matchingObjects = new ArrayList();

        Iterator i = collection.iterator();
        while(i.hasNext()){
            Object object = i.next();

            //checks if the object matches the set of Criterion
            boolean match = this.criterions.match( object );
            
            //if matched add the object to the result
            if(match){
                matchingObjects.add( object );
            }
        }

        if(this.order != null){
            Collections.sort(matchingObjects, order.getComparator());
        }

        List result;
        if(projection != null){
            Object projectionStatus = projection.getStartStatus();
        	for (Object object : matchingObjects) {
        		projectionStatus = projection.project(projectionStatus, object);
        	}
            result = projection.getResult(projectionStatus);
        }else{
            throw new IllegalStateException("Projection is missing");
        }
		return result;
	}

    public Number scalar() {
        List l = this.list();
        if(l!=null && l.size() == 1 && l.get(0) instanceof Number){
            return (Number)(l.get(0));
        }else{
            throw new RuntimeException("The result cannot be converted to a scalar value. Try using list().");
        }
    }

    public Criteria addOrder(Order order) {
        this.order = order;
        return this;
    }

	public Criteria setProjection(Projection projection) {
		this.projection = projection;
		return this;
	}

}